﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Nolineq.Methods;
using ArithmeticLibrary;
using System.Threading;
using System.IO;

namespace Nolineq
{
    /// <summary>
    /// Hlavni okno programu.
    /// </summary>
    public partial class MainForm : Form
    {
        BackgroundWorker bw = new BackgroundWorker();

        StringBuilder sb = new StringBuilder();
        
        #region Fields

        /// <summary>
        /// Funkce.
        /// </summary>
        Function function;

        /// <summary>
        /// Resic rovnice.
        /// </summary>
        EquationSolver solver;

        /// <summary>
        /// Zacatek intervalu.
        /// </summary>
        double start;

        /// <summary>
        /// Konec intervalu.
        /// </summary>
        double end;

        /// <summary>
        /// Prvni aproximace.
        /// </summary>
        double root;

        /// <summary>
        /// Presnost.
        /// </summary>
        double precision;

        /// <summary>
        /// Maximalni pocet iteraci.
        /// </summary>
        int maxSteps;

        /// <summary>
        /// Pocet desetinnych mist vysledku.
        /// </summary>
        int decimalPlaces;

        #endregion

        #region Properties

        /// <summary>
        /// Vrati funkci.
        /// </summary>
        public Function Function { get { return function; } }

        /// <summary>
        /// Vrati zacatek intervalu.
        /// </summary>
        public double Start { get { return start; } }

        /// <summary>
        /// Vrati konec intervalu.
        /// </summary>
        public double End { get { return end; } }

        /// <summary>
        /// Vrati prvni aproximaci.
        /// </summary>
        public double Root { get { return root; } }

        /// <summary>
        /// Vrati presnost.
        /// </summary>
        public double Precision { get { return precision; } }

        /// <summary>
        /// Vrati maximalni pocet iteraci.
        /// </summary>
        public int MaxSteps { get { return maxSteps; } }

        #endregion // Properties

        #region Constructors

        /// <summary>
        /// Vytvori formular hlavniho okna.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            bw.WorkerReportsProgress = false;
            bw.WorkerSupportsCancellation = false;
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        }

        #endregion // Constructors

        #region Form methods

        /// <summary>
        /// Ukonceni aplikace obstaravan pres dialogove okno.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void frmHlavni_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                if (
                    MessageBox.Show("Opravdu chce ukončit?", "Upozornění", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                    == DialogResult.No)
                {
                    e.Cancel = true;
                }
            }
        }

        /// <summary>
        /// Vyresi zadanou nelinearni rovnici.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnVyresit_Click(object sender, EventArgs e)
        {
            // nacteme nastaveni
            LoadConfig();

            // kontrola zadane funkce
            if (tbFunction.Text == "")
            {
                MessageBox.Show("Musíte zadat funkci.");
                return;
            }

            // zparsujeme vyraz a postavime aritmeticky strom
            Expression expression = null;
            try
            {
                expression = new Expression(tbFunction.Text);
            }
            catch (Exception)
            {
                MessageBox.Show("Chybný výraz.");
                return;
            }

            // vytvorime funkci
            try
            {
                this.function = new Function(expression);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return;
            }

            double presnost;
            // Jestli zadal spatne presnost, ktery se neda parsnout
            if (!double.TryParse(tbPrecision.Text, out presnost))
            {
                MessageBox.Show("Neplatná přesnost.");
                return;
            }
            if (presnost < 0 || presnost > 0.1)
            {
                MessageBox.Show("Přesnost musí být číslo z intervalu (0; 0,1].");
                return;
            }

            this.precision = presnost;

            sb.Clear();
            btnSolve.Enabled = false;

            // zavolame BW pro provedeni vypoctu
            if (!bw.IsBusy)
            {
                bw.RunWorkerAsync();
            }

        }

        /// <summary>
        /// Vypocet jednou zvolenou metodou.
        /// </summary>
        private void SingleMethod()
        {
            // jestli je zaskrtnuta Newtonova metoda nebo Newtnova damping metoda
            if (rbNewton.Checked || rbDamping.Checked)
            {
                double koren;
                if (!double.TryParse(tbRoot.Text, out koren))
                {
                    MessageBox.Show("Neplatný počáteční kořen.");
                    return;
                }

                this.root = koren;

                string s = tbFunction.Text + " = 0, počáteční kořen: " + tbRoot.Text +
                    ", přesnost: " + tbPrecision.Text;

                sb.AppendLine(s);
                sb.AppendLine();
            }
            else
            {
                double zacatek;
                // Jestli uzivatel spatne zadal zacatek intervalu, ktery se neda parsnout.
                if (!double.TryParse(tbStart.Text, out zacatek))
                {
                    MessageBox.Show("Neplatný začátek intervalu.");
                    return;
                }

                double konec;
                // Jestli zadal konec intervalu, ktery se neda parsnout.
                if (!double.TryParse(tbEnd.Text, out konec))
                {
                    MessageBox.Show("Neplatný konec intervalu.");
                    return;
                }

                if (zacatek > konec)
                {
                    MessageBox.Show("Konec intervalu musí být větší než začátek intervalu.");
                    return;
                }

                this.start = zacatek;
                this.end = konec;

                string s = tbFunction.Text + " = 0, interval: [" + tbStart.Text + "; " + tbEnd.Text +
                    "], přesnost: " + tbPrecision.Text;

                sb.AppendLine(s);
                sb.AppendLine();
            }

            // vytvorime instanci resice
            this.solver = MethodFactory.CreateSolver(GetSelectedType(), function, start, end, root, precision, maxSteps);

            // Jestli je vsechno spravne zadano, pak budeme pocitat.
            try
            {
                string format = "f" + decimalPlaces;

                Solution reseni = solver.Solve();
                string s = "(" + solver.Name + "): Nalezen kořen " + reseni.Result.ToString(format) + " (" + reseni.Steps + " iterací).";
                sb.AppendLine(s);
                sb.AppendLine();
            }
            catch (NoSolutionException ex)
            {
                string s = "(" + solver.Name + "): " + ex.Message;
                sb.AppendLine(s);
                sb.AppendLine();
            }
            catch (UnsuitableMethodException ex)
            {
                string s = "(" + solver.Name + "): " + ex.Message;
                sb.AppendLine(s);
                sb.AppendLine();
            }
            catch (Exception ex)
            {
                string s = "(" + solver.Name + "): " + ex.Message;
                sb.AppendLine(s);
                sb.AppendLine();
            }

        }

        /// <summary>
        /// Pocitani vice metodami.
        /// </summary>
        private void MultiMethod()
        {
            double zacatek;
            // Jestli uzivatel spatne zadal zacatek intervalu, ktery se neda parsnout.
            if (!double.TryParse(tbStart.Text, out zacatek))
            {
                MessageBox.Show("Neplatný začátek intervalu.");
                return;
            }

            double konec;
            // Jestli zadal konec intervalu, ktery se neda parsnout.
            if (!double.TryParse(tbEnd.Text, out konec))
            {
                MessageBox.Show("Neplatný konec intervalu.");
                return;
            }

            if (zacatek > konec)
            {
                MessageBox.Show("Konec intervalu musí být větší než začátek intervalu.");
                return;
            }

            this.start = zacatek;
            this.end = konec;
            this.root = (zacatek + konec) / 2;

            string s = tbFunction.Text + " = 0, interval: [" + tbStart.Text + "; " + tbEnd.Text +
                "], počáteční kořen: " + root + ", přesnost: " + tbPrecision.Text;

            sb.AppendLine(s);
            sb.AppendLine();

            if (cbSimple.Checked)
            {
                SimpleMethod simple = new SimpleMethod(function, start, end, root, precision, maxSteps);
                RunSolver(simple);
            }
            if (cbBisection.Checked)
            {
                BisectionMethod bisection = new BisectionMethod(function, start, end, root, precision, maxSteps);
                RunSolver(bisection);
            }
            if (cbSecant.Checked)
            {
                SecantMethod secant = new SecantMethod(function, start, end, root, precision, maxSteps);
                RunSolver(secant);
            }
            if (cbNewton.Checked)
            {
                NewtonsMethod newton = new NewtonsMethod(function, start, end, root, precision, maxSteps);
                RunSolver(newton);
            }
            if (cbCombi.Checked)
            {
                NewtonsHybridMethod combi = new NewtonsHybridMethod(NewtonsHybridMethod.SlowMethodType.SecantMethod,
                    function, start, end, root, precision, maxSteps);
                RunSolver(combi);
            }
            if (cbDamping.Checked)
            {
                NewtonsDampingMethod damping = new NewtonsDampingMethod(function, start, end, root, precision, maxSteps);
                RunSolver(damping);
            }
        }

        /// <summary>
        /// Provedeni prace backgroundworkeru.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            // vetveni, jesli se pocita jednou metodou nebo vsemi
            if (rbSingle.Checked) SingleMethod();
            else MultiMethod();
        }

        /// <summary>
        /// Provede vypocet solveru.
        /// </summary>
        /// <param name="solver"></param>
        private void RunSolver(EquationSolver solver)
        {
            try
            {
                string format = "f" + decimalPlaces;

                Solution reseni = solver.Solve();
                string s = "(" + solver.Name + "): Nalezen kořen " + reseni.Result.ToString(format) + 
                    " (" + reseni.Steps + " iterací).";
                sb.AppendLine(s);
                sb.AppendLine();
            }
            catch (NoSolutionException ex)
            {
                string s = "(" + solver.Name + "): " + ex.Message;
                sb.AppendLine(s);
                sb.AppendLine();
            }
            catch (UnsuitableMethodException ex)
            {
                string s = "(" + solver.Name + "): " + ex.Message;
                sb.AppendLine(s); 
                sb.AppendLine();
            }
            catch (Exception ex)
            {
                string s = "(" + solver.Name + "): " + ex.Message;
                sb.AppendLine(s); 
                sb.AppendLine();
            }
        }

        /// <summary>
        /// Dokonceni backgroundworkeru.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            tbResults.AppendText(sb.ToString());
            LogLine();

            // posune caret na konec textu
            tbResults.SelectionStart = tbResults.TextLength;
            // scrollne textbox na caret (tedy na konec)
            tbResults.ScrollToCaret();

            btnSolve.Enabled = true;
        }

        /// <summary>
        /// Okno pro vykresleni grafu funkce.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDraw_Click(object sender, EventArgs e)
        {
            DrawingForm draw = new DrawingForm(tbFunction.Text, tbStart.Text, tbEnd.Text);
            draw.Show();
        }

        #endregion // Form methods

        #region Menu methods

        /// <summary>
        /// Volba reseni nove rovnice.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void novéToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Zepta se jestli opravdu chceme zacit nove reseni.
            if (
                MessageBox.Show("Opravdu chcete započít nový výpočet? Po vymazání se ztratí data o výpočtech z logu.", 
                "Upozornění", MessageBoxButtons.YesNo) == DialogResult.Yes)
            {
                // vymazeme log
                tbResults.Text = ""; 
            }
        }

        /// <summary>
        /// Ulozi log vypoctu do .txt souboru.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uložitLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // kontrola jestli je co ukladat
            if (tbResults.Text == "")
            {
                MessageBox.Show("Log je prázdný.");
                return;
            }

            string format = "yy.MM.dd-hh.mm.ss";

            string date = DateTime.Now.ToString(format);

            date = date.Replace(" ", "");

            string name = "log-" + date + ".txt";

            StreamWriter sw = new StreamWriter(name);

            string[] sep = new string[] { "\n" };

            string[] tokens = tbResults.Text.Split(sep, StringSplitOptions.None);

            for (int i = 0; i < tokens.Length; i++)
            {
                sw.WriteLine(tokens[i]);
            }

            sw.Close();

            MessageBox.Show("Log uložen do souboru: " + name);
        }

        /// <summary>
        /// Okno nastaveni.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void možnostiToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ConfigForm config = new ConfigForm();
            config.Show();
        }

        /// <summary>
        /// Informace o programu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void oProgramuToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutForm about = new AboutForm();
            about.ShowDialog();
        }

        /// <summary>
        /// Ukonceni aplikace pres nabidku v Hlavnim menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void konecToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Nacte nastaveni ze souboru config.cfg.
        /// </summary>
        private void LoadConfig()
        {
            FileStream fs = null;
            StreamReader sr = null;

            try
            {
                // zkusime najit a otevrit config soubor
                fs = new FileStream("config.cfg", FileMode.Open);
                sr = new StreamReader(fs);

                string line = sr.ReadLine();

                string[] sep = new string[] { "=" };

                string[] tokens = line.Split(sep, StringSplitOptions.RemoveEmptyEntries);

                // nenasli jsme slovo steps
                if (tokens[0] != "steps")
                    maxSteps = 1000000;
                // nasli jsme slovo steps
                else 
                { 
                    int i;
                    // hodnota steps neni int
                    if (Int32.TryParse(tokens[1], out i))
                    {
                        // i je platny pocet kroku
                        if (i > 0)
                            maxSteps = i;
                        // i neni platny pocet kroku
                        else
                            maxSteps = 1000000;
                    }
                    else 
                        maxSteps = 1000000;
                }

                line = sr.ReadLine();

                tokens = line.Split(sep, StringSplitOptions.RemoveEmptyEntries);

                // nenasli jsme slovo float
                if (tokens[0] != "float")
                {
                    decimalPlaces = 5;
                }
                // nasli jsme slovo float
                else
                {
                    int i;
                    // hodnota float neni int
                    if (Int32.TryParse(tokens[1], out i))
                    {
                        // i je platny pocet kroku
                        if (i > 0 && i < 11)
                            decimalPlaces = i;
                        // i neni platny pocet kroku
                        else
                            decimalPlaces = 5;
                    }
                    else
                        decimalPlaces = 5;
                }

                sr.Close();
                fs.Close();
            }
            catch (Exception)
            {
                if (sr != null) sr.Close();
                if (fs != null) fs.Close();

                maxSteps = 1000000;
                decimalPlaces = 5;
            }
        }

        /// <summary>
        /// Zjisti ktera metoda je zvolena z id radio buttonu.
        /// </summary>
        /// <returns>Vrati typ zvolene metody.</returns>
        MethodFactory.Type GetSelectedType()
        {
            foreach (Control ctrl in gbRadioMethods.Controls)
            {
                if (ctrl is RadioButton && ((RadioButton)ctrl).Checked)
                {
                    MethodFactory.Type t = (MethodFactory.Type)ctrl.Tag;
                    return t;
                }
            }

            throw new Exception("Nic není vybráno.");
        }

        /// <summary>
        /// Zapise do logu oddelujici caru o dane delce.
        /// </summary>
        /// <param name="length">Delka cary.</param>
        private void LogLine()
        {
            //tbResults.Text += "\n";

            tbResults.Text += "------------------------------------------------------------------------------------------------";

            tbResults.Text += "\n";
        }

        #endregion

        #region RadioButton methods

        private void rbSingle_CheckedChanged(object sender, EventArgs e)
        {
            if (rbSingle.Checked)
            {
                gbRadioMethods.Visible = true;

                rbSimple.Checked = true;

                lblStart.Visible = true;
                lblEnd.Visible = true;
                tbStart.Visible = true;
                tbEnd.Visible = true;

                lblRoot.Visible = false;
                tbRoot.Visible = false;
            }
            else
            {
                gbRadioMethods.Visible = false;
            }
        }

        private void rbMulti_CheckedChanged(object sender, EventArgs e)
        {
            if (rbMulti.Checked)
            {
                gbCheckMethods.Visible = true;

                lblStart.Visible = true;
                lblEnd.Visible = true;
                tbStart.Visible = true;
                tbEnd.Visible = true;

                lblRoot.Visible = false;
                tbRoot.Visible = false;
            }
            else
            {
                gbCheckMethods.Visible = false;

            }
        }

        private void rbDampingNewton_CheckedChanged(object sender, EventArgs e)
        {
            if (rbDamping.Checked)
            {
                tbRoot.Visible = true;
                lblRoot.Visible = true;
            }
            else
            {
                tbRoot.Visible = false;
                lblRoot.Visible = false;
            }
        }

        private void rbNewton_CheckedChanged(object sender, EventArgs e)
        {
            if (rbNewton.Checked)
            {
                tbRoot.Visible = true;
                lblRoot.Visible = true;
            }
            else
            {
                tbRoot.Visible = false;
                lblRoot.Visible = false;
            }
        }

        private void rbJednoducha_CheckedChanged(object sender, EventArgs e)
        {
            if (rbSimple.Checked)
            {
                lblStart.Visible = true;
                lblEnd.Visible = true;

                tbStart.Visible = true;
                tbEnd.Visible = true;
            }
            else
            {
                lblStart.Visible = false;
                lblEnd.Visible = false;

                tbStart.Visible = false;
                tbEnd.Visible = false;
            }
        }

        private void rbBisekce_CheckedChanged(object sender, EventArgs e)
        {
            if (rbBisection.Checked)
            {
                lblStart.Visible = true;
                lblEnd.Visible = true;

                tbStart.Visible = true;
                tbEnd.Visible = true;
            }
            else
            {
                lblStart.Visible = false;
                lblEnd.Visible = false;

                tbStart.Visible = false;
                tbEnd.Visible = false;
            }
        }

        private void rbRegulaFalsi_CheckedChanged(object sender, EventArgs e)
        {
            if (rbSecant.Checked)
            {
                lblStart.Visible = true;
                lblEnd.Visible = true;

                tbStart.Visible = true;
                tbEnd.Visible = true;
            }
            else
            {
                lblStart.Visible = false;
                lblEnd.Visible = false;

                tbStart.Visible = false;
                tbEnd.Visible = false;
            }
        }

        private void rbCombi_CheckedChanged(object sender, EventArgs e)
        {
            if (rbCombi.Checked)
            {
                lblStart.Visible = true;
                lblEnd.Visible = true;

                tbStart.Visible = true;
                tbEnd.Visible = true;
            }
            else
            {
                lblStart.Visible = false;
                lblEnd.Visible = false;

                tbStart.Visible = false;
                tbEnd.Visible = false;
            }
        }

        #endregion // RadioButton methods

        

    }
}
